﻿using System;
using System.Collections.Generic;

namespace Ratatoskr
{
    public class Component : IDisposable
    {

        #region Fields & Properties

        /// <summary>
        /// Return the string used to recognize this type of component. An entity
        /// can only register one component of each type. Components with the
        /// same Type property should implicitly cast to a shared object type.
        /// </summary>
        protected readonly string _type;
        public string Type { get { return _type; } }

        protected ParentEntity _parent;
        /// <summary>
        /// ParentEntity attached to this component.
        /// </summary>
        public ParentEntity Parent
        {
            get { return _parent; }
        }

        protected bool _initialized;
        /// <summary>
        /// Initialization status.
        /// </summary>
        public bool Initialized
        {
            get { return _initialized; }
        }

        protected internal struct LinkProfile
        {
            public EventHandler<ServerComponentEventArgs> handler;
            public float priority;
            public LinkProfile(EventHandler<ServerComponentEventArgs> h, float p)
            {
                handler = h;
                priority = p;
            }
        }

        protected Dictionary<string, LinkProfile> _serverProfiles;
        /// <summary>
        /// ServerComponent types this Component searches for.
        /// </summary>
        public string[] ServerTypes
        {
            get
            {
                if (_serverProfiles == null)
                    return new string[0];
                string[] s = new string[_serverProfiles.Count];
                _serverProfiles.Keys.CopyTo(s, 0);
                return s;
            }
        }

        protected Dictionary<string, ServerComponent> _servers;
        /// <summary>
        /// ServerComponents this Component is currently subscribed to.
        /// </summary>
        public ServerComponent[] Servers
        {
            get
            {
                if (_servers == null)
                    return new ServerComponent[0];
                ServerComponent[] s = new ServerComponent[_servers.Count];
                int i = 0;
                foreach (ServerComponent k in _servers.Values)
                    s[i++] = k;
                return s;
            }
        }
        
        #endregion

        #region Construction, Parent Management & Initialization

        public Component(string type)
        {
            if (type == null)
                throw new ArgumentNullException("Cannot create a component with null type.");
            _type = type;
        }

        /// <summary>
        /// Attach a ParentEntity to this component, registering in the parent's Components list and subscribing to relevant events.
        /// </summary>
        /// <param name="parent">ParentEntity to attach.</param>
        /// <returns></returns>
        public bool Attach(ParentEntity parent)
        {
            if (parent == null)
                throw new ArgumentNullException("Attempted to attach a null parent.");
            if (_parent != null)
                return false;
            if (parent.Register(this))
            {
                _parent = parent;
                _parent.ParentChanged += HandleParentChanged;
                ResolveServers();
                ExtendAttach();
                Initialize();
                return true;
            }
            return false;
        }

        /// <summary>
        /// Detach the current ParentEntity (if any), clearing registration and subscriptions.
        /// </summary>
        public void Detach()
        {
            if (_parent != null)
            {
                ExtendDetach();
                _parent.ParentChanged -= HandleParentChanged;
                _parent.Sever(this);
                foreach (KeyValuePair<string, ServerComponent> s in _servers)
                    s.Value.Sever(this);
            }
            _parent = null;
            _servers = null;
            _initialized = false;
        }

        protected void HandleParentChanged(object sender, EventArgs e)
        {
            ResolveServers();
            ExtendHandleParentChanged(sender, e);
        }

        public bool Initialize()
        {
            if (_parent.Initialized)
                if (!_initialized)
                {
                    _initialized = true;
                    ExtendInitialize();
                }
            return _initialized;
        }

        #endregion

        #region ServerComponent Link Management

        /// <summary>
        /// Add a ServerComponent link profile, automatically registering with the nearest matching server in the parent chain.
        /// </summary>
        /// <param name="type">Server type identifier string.</param>
        /// <param name="handler">Method to handle this server type's trigger.</param>
        /// <param name="priority">Order of handler processing, lower values called first.</param>
        /// <returns></returns>
        public bool AddServerProfile(string type, EventHandler<ServerComponentEventArgs> handler, float priority = 0)
        {
            if (type == null)
                throw new ArgumentNullException("Tried to add a server profile with a null type string.");
            if (handler == null)
                throw new ArgumentNullException("Tried to assign a null handler to server type " + type + ".");
            if (_serverProfiles == null)
                _serverProfiles = new Dictionary<string, LinkProfile>();
            else if (_serverProfiles.ContainsKey(type))
                return false;
            LinkProfile l = new LinkProfile(handler, priority);
            _serverProfiles.Add(type, l);
            AttachServer(FindServer(type), l);
            return true;
        }

        /// <summary>
        /// Remove a ServerComponent link profile and detach from any registered server.
        /// </summary>
        /// <param name="type">Server type identifier string.</param>
        public void RemoveServerProfile(string type)
        {
            if (_serverProfiles != null)
            {
                try
                {
                    if (_serverProfiles.ContainsKey(type))
                    {
                        DetachServer(type);
                        _serverProfiles.Remove(type);
                        if (_serverProfiles.Count == 0)
                            _serverProfiles = null;
                    }
                }
                catch (ArgumentNullException e) { throw new ArgumentNullException("Server type string key was null.", e); }
            }
        }

        /// <summary>
        /// Change the priority setting of an existing link profile and update any registered server.
        /// </summary>
        /// <param name="type">Server type identifier string.</param>
        /// <param name="priority">Order of handler processing, lower values called first.</param>
        /// <returns></returns>
        public void SetServerPriority(string type, float priority)
        {
            LinkProfile l;
            try { l = _serverProfiles[type]; }
            catch (ArgumentNullException e) { throw new ArgumentNullException("Server type string key was null.", e); }
            catch (KeyNotFoundException e) { throw new KeyNotFoundException("Requested server was not found.", e); }
            l = new LinkProfile(l.handler, priority);
            _serverProfiles[type] = l;
            if (_servers.ContainsKey(type))
            {
                ServerComponent s = _servers[type];
                s.Sever(this);
                s.Register(this, l);
            }
        }

        public bool HasServer(string type)
        {
            try { return _servers.ContainsKey(type); }
            catch (ArgumentNullException e) { throw new ArgumentNullException("Server type string key was null.", e); }
        }

        public ServerComponent GetServer(string type)
        {
            try { return _servers[type]; }
            catch (ArgumentNullException e) { throw new ArgumentNullException("Server type string key was null.", e); }
            catch (KeyNotFoundException e) { throw new KeyNotFoundException("Requested server was not found.", e); }
        }

        public bool TryGetServer(string type, out ServerComponent server)
        {
            try { return _servers.TryGetValue(type, out server); }
            catch (ArgumentNullException e) { throw new ArgumentNullException("Server type string key was null.", e); }
        }

        public bool HasServerProfile(string type)
        {
            try { return _serverProfiles.ContainsKey(type); }
            catch (ArgumentNullException e) { throw new ArgumentNullException("Server type string key was null.", e); }
        }

        public float GetServerPriority(string type)
        {
            try { return _serverProfiles[type].priority; }
            catch (ArgumentNullException e) { throw new ArgumentNullException("Server type string key was null.", e); }
            catch (KeyNotFoundException e) { throw new KeyNotFoundException("Requested server was not found.", e); }
        }

        public bool TryGetServerPriority(string type, out float priority)
        {
            LinkProfile l;
            try
            {
                if (_serverProfiles.TryGetValue(type, out l))
                {
                    priority = l.priority;
                    return true;
                }
            }
            catch (ArgumentNullException e) { throw new ArgumentNullException("Server type string key was null.", e); }
            priority = 0;
            return false;
        }

        protected void ResolveServers()
        {
            if (_serverProfiles == null)
                return;
            if (_servers == null)
                _servers = new Dictionary<string, ServerComponent>();
            foreach (KeyValuePair<string, LinkProfile> k in _serverProfiles)
            {
                ServerComponent s = FindServer(k.Key);
                if (!_servers.ContainsKey(k.Key))
                    AttachServer(s, k.Value);
                else if (s != _servers[k.Key])
                {
                    _servers[k.Key].Sever(this);
                    if (!AttachServer(s, k.Value))
                        _servers.Remove(k.Key);
                }
            }
        }

        protected ServerComponent FindServer(string t)
        {
            ParentEntity p = _parent;
            while (p != null)
            {
                Component c;
                p.TryGetComponent(t, out c);
                ServerComponent s = c as ServerComponent;
                if (s != null)
                    return s;
                Entity e = p as Entity;
                if (e == null)
                    p = null;
                else p = e.Parent;
            }
            return null;
        }

        protected bool AttachServer(ServerComponent s, LinkProfile l)
        {
            if (s != null)
                if (s.Register(this, l))
                {
                    _servers[s.Type] = s;
                    return true;
                }
            return false;
        }

        protected void DetachServer(string t)
        {
            if (_servers != null)
            {
                ServerComponent s;
                _servers.TryGetValue(t, out s);
                if (s != null)
                {
                    s.Sever(this);
                    _servers.Remove(t);
                    if (_servers.Count == 0)
                        _servers = null;
                }
            }
        }

        #endregion

        #region IDisposable

        public void Dispose()
        {
            Detach();
            ExtendDispose();
        }

        #endregion

        #region Virtual Methods & Implementation Notes

        /// <summary>
        /// Called by Attach() after verifying registration with a new parent, subscribing to its ParentChanged event and searching for ServerComponents, but before attempting initialization.
        /// </summary>
        protected virtual void ExtendAttach() { }

        /// <summary>
        /// Called by Detach() after verifying current parent is not null, but before any other operations.
        /// </summary>
        protected virtual void ExtendDetach() { }

        /// <summary>
        /// Called when there are changes to the Entity hierarchy above this component's immediate parent, after searching for ServerComponents.
        /// </summary>
        protected virtual void ExtendHandleParentChanged(object sender, EventArgs e) { }

        /// <summary>
        /// Called by Initialize() after verifying that this component's state and parent state allow initialization, and setting _initialized to true. There are no subsequent operations.
        /// </summary>
        protected virtual void ExtendInitialize() { }

        /// <summary>
        /// Called by Dispose(), after calling Detach(). There are no subsequent operations. Implement any additional behavior required for clean disposal.
        /// </summary>
        protected virtual void ExtendDispose() { }

        #endregion

    }
}